package hust.cs.javacourse.search.index.impl;

import hust.cs.javacourse.search.index.*;

import java.io.*;
import java.util.*;

/**
 * AbstractIndex的具体实现类
 */
public class Index extends AbstractIndex {
    /**
     * 返回索引的字符串表示
     *
     * @return 索引的字符串表示
     */
    @Override
    public String toString() {
        String info = "docId--------docPath:";
        Iterator it = this.docIdToDocPathMapping.entrySet().iterator();
        Map.Entry entry;
        while (it.hasNext()) {
            entry = (Map.Entry) it.next();
            info = info + "\n" + entry.getKey() + " -----> " +
                    entry.getValue();
        }

        info = info + "\nPostingList: ";
        it = this.termToPostingListMapping.entrySet().iterator();
        AbstractPostingList postingList;
        AbstractTerm term;

        while (it.hasNext()) {
            entry = (Map.Entry) it.next();
            info = info + "\n" + entry.getKey().toString() + " -----> " +
                    entry.getValue().toString();
        }
        return info;
    }

    /**
     * 添加文档到索引，更新索引内部的HashMap
     *
     * @param document ：文档的AbstractDocument子类型表示
     */
    @Override
    public void addDocument(AbstractDocument document) {
        int size = document.getTupleSize();
        AbstractTerm term;
        int docId = document.getDocId();
        int index;
        int pos;
        AbstractPosting posting;
        AbstractPostingList postingList;

        int freq;
        this.docIdToDocPathMapping.put(docId, document.getDocPath());

        for(int i =0;i < size;i++) {
            List<Integer> positions = new ArrayList<>();
            term = document.getTuple(i).term;
            pos = document.getTuple(i).curPos;
            if(this.termToPostingListMapping.containsKey(term)) {
                postingList = termToPostingListMapping.get(term);
                //System.out.println(postingList.toString());
                index = postingList.indexOf(docId);
                if(index == -1) {
                    positions.add(pos);
                    posting = new Posting(docId, 1, positions);
                    postingList.add(posting);
                }
                else {
                    posting = postingList.get(index);
                    freq = posting.getFreq();
                    posting.getPositions().add(pos);
                    posting.setFreq(freq + 1);
                }
            } else {
                positions.add(pos);
                posting = new Posting(docId, 1, positions);
                postingList = new PostingList();
                postingList.add(posting);
                this.termToPostingListMapping.put(term, postingList);
            }
        }
    }

    /**
     * <pre>
     * 从索引文件里加载已经构建好的索引.内部调用FileSerializable接口方法readObject即可
     * @param file ：索引文件
     * </pre>
     */
    @Override
    public void load(File file) {
        try {
            ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
            readObject(in);
        }  catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * <pre>
     * 将在内存里构建好的索引写入到文件. 内部调用FileSerializable接口方法writeObject即可
     * @param file ：写入的目标索引文件
     * </pre>
     */
    @Override
    public void save(File file) {
        try {
            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
            writeObject(out);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 返回指定单词的PostingList
     *
     * @param term : 指定的单词
     * @return ：指定单词的PostingList;如果索引字典没有该单词，则返回null
     */
    @Override
    public AbstractPostingList search(AbstractTerm term) {
        AbstractPostingList postingList = this.termToPostingListMapping.get(term);
        return postingList;
    }

    /**
     * 返回索引的字典.字典为索引里所有单词的并集
     *
     * @return ：索引中Term列表
     */
    @Override
    public Set<AbstractTerm> getDictionary() {
        Set<AbstractTerm> termSet = new TreeSet<>();
        Map.Entry entry;
        Iterator it = this.termToPostingListMapping.entrySet().iterator();
        while(it.hasNext()) {
            entry = (Map.Entry) it.next();
            termSet.add((AbstractTerm) entry.getKey());
        }
        return termSet;
    }

    /**
     * <pre>
     * 对索引进行优化，包括：
     *      对索引里每个单词的PostingList按docId从小到大排序
     *      同时对每个Posting里的positions从小到大排序
     * 在内存中把索引构建完后执行该方法
     * </pre>
     */
    @Override
    public void optimize() {
        Map.Entry entry;
        AbstractPostingList postingList;
        //AbstractPosting posting;
        Iterator it = this.termToPostingListMapping.entrySet().iterator();
        while(it.hasNext()) {
            entry = (Map.Entry) it.next();
            postingList = (AbstractPostingList) entry.getValue();
            postingList.sort();
            for(int i = 0;i < postingList.size();i++) {
                postingList.get(i).getPositions().sort(Integer::compareTo);
            }
        }
    }

    /**
     * 根据docId获得对应文档的完全路径名
     *
     * @param docId ：文档id
     * @return : 对应文档的完全路径名
     */
    @Override
    public String getDocName(int docId) {
        return this.docIdToDocPathMapping.get(docId);
    }

    /**
     * 写到二进制文件
     *
     * @param out :输出流对象
     */
    @Override
    public void writeObject(ObjectOutputStream out) {
        try {
            out.writeObject(this.termToPostingListMapping);
            out.writeObject(this.docIdToDocPathMapping);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 从二进制文件读
     *
     * @param in ：输入流对象
     */
    @Override
    public void readObject(ObjectInputStream in) {
        try {
            this.termToPostingListMapping = (Map) (in.readObject());
            this.docIdToDocPathMapping = (Map) (in.readObject());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
